
#include "EggNinePCH.h"

#include "waveMesh.h"
#include "waveTechnique.h"
#include "presentTechnique.h"

#include "CImg.h"

using namespace cimg_library;

namespace EggNine {

#define g 9.81f
#define rho 997.0479f

WaveMesh::WaveMesh(int cells, float L, float c, float dt)
    :PresentableMesh(PRESENT_TESSELATED)
    ,SimulateableMesh(SIMULATE_2D)
    ,m_partDims(cells)
    ,m_baseTexIdx(0)
{
    glGenTextures(sizeof(m_texs)/sizeof(GLuint), m_texs);

    const int halfBoundary=20;
    m_fullDims=cells+halfBoundary*2;

    const GLint swizzles[] = {GL_RED, GL_RED, GL_RED, GL_RED};
    m_impulse=CImg<float>(m_fullDims, m_fullDims, 1, 1, .0f);
    glBindTexture(GL_TEXTURE_2D, m_texs[4]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, m_fullDims, m_fullDims, 0, GL_RED, GL_FLOAT, m_impulse.data());
    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzles);
    
    for(int i=0;i<3;i++) {
        glBindTexture(GL_TEXTURE_2D, m_texs[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, m_fullDims, m_fullDims, 0, GL_RED, GL_FLOAT, m_impulse.data());
        glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzles);
        glGenerateMipmap(GL_TEXTURE_2D);
    }

    const float bRatio=3.f;
    m_dh=L/(m_fullDims-1);
    CImg<float> N(m_fullDims, m_fullDims, 1, 1, 1.f);
    for(int i=0;i<m_fullDims;i++)
        for(int j=0;j<m_fullDims;j++)
            if((i<halfBoundary || i>=m_fullDims-halfBoundary) || (j<halfBoundary || j>=m_fullDims-halfBoundary))
                N(i,j)=bRatio;

    glBindTexture(GL_TEXTURE_2D, m_texs[3]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, m_fullDims, m_fullDims, 0, GL_RED, GL_FLOAT, N.data());
    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzles);

    const float rotAngle=-(float)M_PI/2;
    const float a=sin(rotAngle*.5f), b=cos(rotAngle*.5f);
    m_modelData.ModelView=mat4();
    m_modelData.ModelView(0,0)=10;
    m_modelData.ModelView(1,1)=10;
    m_modelData.ModelView(2,2)=10;
    m_modelData.ModelView*=RotationMatrixFromQ(vec4(a,0.f,0.f,b));
    m_modelData.ModelView(1,3)=-2.0f;
    m_modelData.ModelView(2,3)=-5.0f;
    m_modelData.ModelView.NormalMatrix(m_modelData.NormalM);
    m_materialData.diffuse[2]=.8f;  // b channel
    m_materialData.diffuse[3]=1.5f; // roughness
    m_materialData.extra[0]=700.f;    // height scale

    m_wParams.cSq=c*c;
    m_wParams.dt=dt;
    m_wParams.invDhSq=1.f/m_dh/m_dh;
    m_wParams.impulsePre=1.f/g/rho;

    glGenVertexArrays(1, &m_vaoId);
    glGenBuffers(1, &m_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
    const float step=min(64.f/m_partDims, 1.f);
    m_nVertices=(int)ceilf(1.0f/step)+1;
    float *vertices = new float[m_nVertices*m_nVertices*6];
    const float texStart=(float)halfBoundary/m_fullDims;
    for(int i=0;i<m_nVertices;i++)
        for(int j=0;j<m_nVertices;j++) {
            int loc=6*(i*m_nVertices+j);
            vertices[loc+0]=min(-1.f+2.f*step*i, 1.f);
            vertices[loc+1]=min(-1.f+2.f*step*j, 1.f);
            vertices[loc+2]=0.f;
            vertices[loc+3]=1.f;
            vertices[loc+4]=(1.f-2.f*texStart)*min(step*i, 1.f)+texStart;
            vertices[loc+5]=(1.f-2.f*texStart)*min(step*j, 1.f)+texStart;
        }

    glBufferData(GL_ARRAY_BUFFER, sizeof(float)*6*m_nVertices*m_nVertices, vertices, GL_STATIC_DRAW);
    delete[] vertices;

    glGenBuffers(1, &m_indx);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indx);
    unsigned short *indices = new unsigned short[(m_nVertices-1)*(m_nVertices-1)*4];
    for(int i=0;i<m_nVertices-1;i++)
        for(int j=0;j<m_nVertices-1;j++) {
            int loc=4*(i*(m_nVertices-1)+j);
            indices[loc+0]=(i+1)*m_nVertices+j;
            indices[loc+1]=(i+1)*m_nVertices+j+1;
            indices[loc+2]=i*m_nVertices+j;
            indices[loc+3]=i*m_nVertices+j+1;
        }
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*(m_nVertices-1)*(m_nVertices-1)*4, indices, GL_STATIC_DRAW);
    delete[] indices;
}

WaveMesh::~WaveMesh()
{
    glDeleteTextures(sizeof(m_texs)/sizeof(GLuint), m_texs);
    glDeleteBuffers(1, &m_vbo);
    glDeleteBuffers(1, &m_indx);
    glDeleteVertexArrays(1, &m_vaoId);
}

void WaveMesh::AttachUniforms(const UniformMap& uBlocks)
{
    m_uniformModel=static_cast<const ModelBlock*>(uBlocks.at("Model"));
    m_uniformMaterial=static_cast<const MaterialBlock*>(uBlocks.at("MaterialAttrs"));
    m_wSimBlock=static_cast<const WaveSimBlock*>(uBlocks.at("WaveSimParams"));
}

void WaveMesh::MapAttributes(const ProgramsMap& tMap)
{
    const IProgram* pEffect=tMap.at("TessPresent");
    m_Position=pEffect->GetAttribLocation("Position");
    m_TexCoords=pEffect->GetAttribLocation("TexCoord");

    glBindVertexArray(m_vaoId);
    
    glVertexAttribFormat(m_Position, 4, GL_FLOAT, GL_FALSE, 0);
    glVertexAttribFormat(m_TexCoords, 2, GL_FLOAT, GL_FALSE, sizeof(float)*4);

    glVertexAttribBinding(m_Position, 0);
    glVertexAttribBinding(m_TexCoords, 0);

    glBindVertexBuffer(0, m_vbo, 0, sizeof(float)*6);

    glEnableVertexAttribArray(m_Position);
    glEnableVertexAttribArray(m_TexCoords);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indx);

    glBindVertexArray(0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void WaveMesh::Simulate(float dt, const IProgram* pTechnique)
{
    static bool impulseMade=false;

    if(!impulseMade) {
        const int start=m_fullDims/4;
//         m_impulse=1.f;
        m_impulse(start,start)=5000.f;
        m_impulse.blur(5);
        m_impulse*=.001f/(25*m_dh); // T/Area

        glBindTexture(GL_TEXTURE_2D, m_texs[4]);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_fullDims, m_fullDims, GL_RED, GL_FLOAT, m_impulse.data());
    }

    const WaveSolveTechnique* simTechnique=static_cast<const WaveSolveTechnique*>(pTechnique);

    const int mod3Table[] = {0, 1, 2, 0};
    m_wSimBlock->SetData(&m_wParams);
    m_wSimBlock->Update();

    simTechnique->SetTextureSize(m_fullDims, m_fullDims);
    simTechnique->SetTexture("u_t", m_texs[m_baseTexIdx]);
    m_baseTexIdx=mod3Table[++m_baseTexIdx];
    simTechnique->SetTexture("u_pt", m_texs[m_baseTexIdx]);
    m_baseTexIdx=mod3Table[++m_baseTexIdx];
    simTechnique->SetTargetTexture(m_texs[m_baseTexIdx], GL_TEXTURE_2D);
    simTechnique->SetTexture("u_stepConst", m_texs[3]);
    simTechnique->SetTexture("impulse", m_texs[4]);
    simTechnique->ResetToProgram();
    simTechnique->DrawPass();

    glBindTexture(GL_TEXTURE_2D, m_texs[m_baseTexIdx]);
    glGenerateMipmap(GL_TEXTURE_2D);

    if(!impulseMade) {
        impulseMade=true;
        m_impulse=0.f;
        glBindTexture(GL_TEXTURE_2D, m_texs[4]);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_fullDims, m_fullDims, GL_RED, GL_FLOAT, m_impulse.data());
    }
}

void WaveMesh::Draw(const IProgram* pTechnique)
{
    m_uniformModel->SetData(&m_modelData);
    m_uniformModel->Update();
    m_uniformMaterial->SetData(&m_materialData);
    m_uniformMaterial->Update();

    static_cast<const DisplacedQuadTechnique*>(pTechnique)->SetDispTexture(m_texs[m_baseTexIdx]);

    glBindVertexArray(m_vaoId);
    glPatchParameteri(GL_PATCH_VERTICES, 4);
    glDrawElements(GL_PATCHES, (m_nVertices-1)*(m_nVertices-1)*4, GL_UNSIGNED_SHORT, NULL);
}

} // EggNine